import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_time_manager/state/db_help.dart';
import 'package:flutter_time_manager/state/model/todo.dart';
import 'package:flutter_time_manager/state/model/todo_collect.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';

class TimeManager with ChangeNotifier {
  bool _isLoading = true;
  List<TodoCollect> _todoCollectList = [];

  TimeManager() {
    _getTodoCollect();
    _clearHistoryData();
  }

  TodoCollect _currentCollect;

  TodoCollect get currentCollect =>
      _currentCollect == null ? _todoCollectList[0] : _currentCollect;

  List<TodoCollect> get todoCollectList => _todoCollectList;

  int getTodoSize() => currentCollect.todoList.length;

  Todo getTodo(int index) => currentCollect.todoList[index];

  void updateCollectImage(String id) async {
    try {
      File imageFile = await ImagePicker.pickImage(source: ImageSource.gallery);
      _todoCollectList.forEach((todo) async {
        if (todo.id == id) {
          todo.filePath = imageFile.path;
          await _updateCollectFilePath(id, imageFile.path);
          notifyListeners();
        }
      });
    } catch (e) {
      print("error: ${e}");
    }
  }

  void setCurrentCollect(int index) {
    _currentCollect = _todoCollectList[index];
    notifyListeners();
  }

  void saveOrUpdateTodo(bool addAllFlag, Todo todo, String collectId,
      String title, String note, TimeOfDay timeOfDay) {
    for (var collect in _todoCollectList) {
      if (todo != null && collect.id == collectId) {
        _updateTodo(collect, todo, collectId, title, note, timeOfDay);
        notifyListeners();
        break;
      }

      if (todo == null && !addAllFlag && collect.id == collectId) {
        _insertTodo(collect, title, note, timeOfDay);
        notifyListeners();
        break;
      }

      if (todo == null && addAllFlag) {
        _addAllTodo(collect, title, note, timeOfDay);
      }
    }

    if (todo == null && addAllFlag) {
      notifyListeners();
    }
  }

  DateTime _convertTaskTime(DateTime dateTime, TimeOfDay timeOfDay) {
    var hour = timeOfDay.hour;
    if (timeOfDay.period == DayPeriod.am && hour == 0) {
      hour = 12;
    }
    if (timeOfDay.period == DayPeriod.pm && hour == 12) {
      hour = 24;
    }
    return DateTime(
        dateTime.year, dateTime.month, dateTime.day, hour, timeOfDay.minute);
  }

  void _updateTodo(TodoCollect collect, Todo todo, String collectId,
      String title, String note, TimeOfDay timeOfDay) {
    todo.title = title;
    todo.note = note;
    todo.taskTime = _convertTaskTime(collect.dateTime, timeOfDay);
    _updateDb(todo);
    final list = collect.todoList;
    _addAndSortList(list, null);
    collect.todoList = list;
  }

  void _addAndSortList(List<Todo> list, Todo todo) {
    if (todo != null) {
      list.add(todo);
    }
    list.sort((x, y) => x.taskTime.compareTo(y.taskTime));
  }

  void _insertTodo(
      TodoCollect collect, String title, String note, TimeOfDay timeOfDay) {
    final todo = Todo(
        taskTime: _convertTaskTime(collect.dateTime, timeOfDay),
        title: title,
        note: note,
        collectId: collect.id,
        completeFlag: false);
    _insertDb(todo);
    final list = collect.todoList;
    _addAndSortList(list, todo);
    collect.todoList = list;
  }

  void deleteTodo(String todoId) {
    final list = currentCollect.todoList.where((i) => i.id != todoId);
    currentCollect.todoList = list.toList();
    notifyListeners();
  }

  void _addAllTodo(
      TodoCollect collect, String title, String note, TimeOfDay timeOfDay) {
    final list = collect.todoList;
    final todo = Todo(
        title: title,
        note: note,
        taskTime: _convertTaskTime(collect.dateTime, timeOfDay),
        collectId: collect.id);
    _insertDb(todo);
    _addAndSortList(list, todo);
  }

  Future<void> _insertDb(Todo todo) async {
    final db = await dbHelp.database;
    db.rawInsert(
        "insert into todo_list (id, taskTime, title, note, collectId) values (?,?,?,?,?)",
        [
          todo.id,
          todo.taskTime.millisecondsSinceEpoch,
          todo.title,
          todo.note,
          todo.collectId
        ]);
  }

  Future<void> _updateDb(Todo todo) async {
    final db = await dbHelp.database;
    db.rawUpdate(
        "update todo_list set taskTime=?, title=?, note=? where id = ?",
        [todo.taskTime.millisecondsSinceEpoch, todo.title, todo.note, todo.id]);
  }

  Future<List<Todo>> _getTodoList(String collectId) async {
    final db = await dbHelp.database;
    final list = await db.rawQuery(
        "select id, taskTime, title, note, collectId "
        "from todo_list where collectId = ? order by taskTime asc",
        [collectId]);
    return list.map((i) => Todo.fromMap(i)).toList();
  }

  Future<void> _getTodoCollect() async {
    final now = DateTime.now();
    final nowDate = DateTime(now.year, now.month, now.day);
    final db = await dbHelp.database;
    final list = await db.rawQuery(
        "select id, dateTime, filePath, indexNum "
        "from todo_collect where dateTime between ? and ? order by dateTime asc limit 5",
        [
          nowDate.millisecondsSinceEpoch,
          nowDate.add(Duration(days: 5)).millisecondsSinceEpoch - 1
        ]);
    if (list.isNotEmpty) {
      await _checkTodoCollectList(list, nowDate);
    } else {
      await _insertAllCollect();
    }
    _isLoading = false;
    notifyListeners();
  }

  bool get isLoading => _isLoading;

  void _checkTodoCollectList(
      List<Map<String, dynamic>> todoCollectList, DateTime now) async {
    final day = _convertDate(now);

    List<TodoCollect> list = [];
    for (var c in todoCollectList) {
      final collect = TodoCollect.fromMap(c);
      collect.todoList = await _getTodoList(collect.id);
      list.add(collect);
    }

    /// 4, 5, 6, 7, 8
    /// 今天 5 号
    /// 5, 6, 7, 8
    for (int i = list.length; i < 5; i++) {
      final collect =
          TodoCollect(index: _getIndex(list), dateTime: day.add(Duration(days: i)));
      list.add(collect);
      await _insertTodoCollect0(collect);
    }
    _todoCollectList = list;
    _currentCollect = _todoCollectList[0];
  }

  DateTime _convertDate(DateTime dayTime) {
    return DateTime(dayTime.year, dayTime.month, dayTime.day);
  }

  bool _equalsDate(DateTime dayTime, DateTime todoCollectDayTime) {
    return dayTime.year == todoCollectDayTime.year &&
        dayTime.month == todoCollectDayTime.month &&
        dayTime.day == todoCollectDayTime.day;
  }

  void _insertTodoCollect(List<TodoCollect> todoCollectList) async {
    for (var collect in todoCollectList) {
      await _insertTodoCollect0(collect);
    }
  }

  void _insertAllCollect() async {
    _todoCollectList = List.generate(
      5,
      (index) => TodoCollect(
            index: index,
            dateTime: DateTime.now().add(Duration(days: index)),
          ),
    );
    _currentCollect = _todoCollectList[0];
    await _insertTodoCollect(_todoCollectList);
  }

  Future<void> _insertTodoCollect0(TodoCollect collect) async {
    final db = await dbHelp.database;
    await db.rawInsert(
        "insert into todo_collect (id, dateTime, filePath, indexNum) values ("
        "?,?,?,?)",
        [
          collect.id,
          collect.dateTime.millisecondsSinceEpoch,
          collect.filePath,
          collect.index
        ]);
  }

  void _clearHistoryData() {
    Timer(Duration(milliseconds: 5), () async {
      print("clear history data start ...");
      final db = await dbHelp.database;
      final dayTime = _convertDate(DateTime.now()).millisecondsSinceEpoch;
      db.rawDelete("delete from todo_collect where dateTime < ?", [dayTime]);
      db.rawDelete("delete from todo_list where taskTime < ?", [dayTime]);
    });
  }

  Future<void> _updateCollectFilePath(String id, String filePath) async {
    final db = await dbHelp.database;
    await db.rawUpdate(
        "update todo_collect set filePath=? where id = ?", [filePath, id]);
  }

  int _getIndex(List<TodoCollect> list) {
    int nextIndex = list.last.index + 1;
    if ((nextIndex) > 5) {
      nextIndex = 1;
    }
    return nextIndex;
  }
}
